package org.jflame.commons.crypto.digest;

import org.jflame.commons.codec.Transcoder;
import org.jflame.commons.crypto.EncryptException;
import org.jflame.commons.util.StringHelper;

import java.nio.charset.StandardCharsets;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;

import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;

/**
 * PBKDF2 hash算法实现
 * 
 * @author charles.zhang
 */
public class PBKDF2 {

    private final static String ALG_PREFIX = "PBKDF2With";

    private final int iterationCount;
    private final int keyLength;
    private final String alg;

    /**
     * 默认构造函数. 默认值:iterationCount = 2000,keyLength = 256,alg=PBKDF2WithHmac256
     */
    public PBKDF2() {
        this.iterationCount = 2000;
        this.keyLength = 256;
        this.alg = ALG_PREFIX + HmacAlg.HmacSHA256.name();
    }

    /**
     * 构造函数
     * 
     * @param hmac hmac算法名,参考枚举: {@link HmacAlg}. 传空使用HmacSHA256
     * @param iterationCount hash迭代次数
     * @param keyLength 生成密钥长度
     */
    public PBKDF2(String hmac, int iterationCount, int keyLength) {
        this.iterationCount = iterationCount;
        this.keyLength = keyLength;
        if (StringHelper.isEmpty(hmac)) {
            this.alg = ALG_PREFIX + "HmacSHA256";
        } else {
            this.alg = ALG_PREFIX + hmac;
        }
    }

    public PBKDF2(HmacAlg hmac, int iterationCount, int keyLength) {
        this(hmac.name(), iterationCount, keyLength);
    }

    public byte[] hash(char[] passwordChars, byte[] salt) throws EncryptException {
        try {
            PBEKeySpec spec = new PBEKeySpec(passwordChars, salt, iterationCount, keyLength);
            SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(alg);
            return keyFactory.generateSecret(spec)
                    .getEncoded();
        } catch (InvalidKeySpecException | NoSuchAlgorithmException e) {
            throw new EncryptException(e);
        }
    }

    public String hash(String password, String salt) throws EncryptException {
        return Transcoder
                .encodeBase64URLSafeString(hash(password.toCharArray(), salt.getBytes(StandardCharsets.UTF_8)));
    }

    public String getAlg() {
        return alg;
    }

}
